home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-06
/
btp15.zip
/
BTP.DOC
< prev
next >
Wrap
Text File
|
1991-11-09
|
23KB
|
495 lines
BTP.DOC
(C) 1991 John C. Leon
Documentation for BTP V1.5, 11/9/91
BTP - The Btrieve Unit for Turbo Pascal 6.0
(* ------------------------------------------------------------------------ *)
INTRODUCTION TO BTP
-------------------
Btrieve is a record manager sold by Novell. Btrieve has been around a long
time. It takes many forms today...with versions for Dos, Windows 3.0,
OS/2, and a "client/server" form when used with Novell Netware. This unit has
been tested only with Btrieve for Dos, Version 5.10a, with all patches
available thru 10/27/91 applied.
Btrieve files have no inherent structure to differentiate fields. Your own
code provides that functionality. The BTP product is the only programmer's
toolkit of its kind (to my knowledge) available to TP/Btrieve programmers
offering an extra-ordinarily simple means of Btrieve file management...a
well-thought out design of data structures and a complete guide (through the
example programs) to deal with Btrieve files as objects. Further, BTP
provides the means to make any Btrieve call SIMPLY and with CONFIDENCE, with
encapsulation of your file's position block. The most intriguing capability
for many will be the ability to use the relatively new get/step extended calls
WITH EASE; it is these more complex, yet important functions, that daunt even
the most ardent fans of Btrieve. NO MORE!
The "universal" Btrieve call, which is made by calling a TP function supplied
by Novell with the Btrieve product, takes six parameters. Keeping track of
multiple Btrieve files and the variables for all six parameters for each
file in a complex application using traditional programming techniques takes
discipline and a masochistic attention to detail; I consider myself
disciplined but I'm NOT masochistic. Btrieve programming screams out for the
simplification possible with object-oriented programming and the record
structures provided by Turbo Pascal 6.0.
The BTP product was born to remove the drudgery from Btrieve programming. BTP
includes a number of sound data structures/objects, including these four:
TRecMgr, BFile, BFixed, and BFileExt. TRecMgr and BFile are base objects;
they are direct descendants of TP6's TObject. BFixed and BFileExt are both
direct descendants of BFile.
TRECMGR is of limited usefulness (see the unit source code and VERSION.PAS),
------- but it does provide the ability to make non-file oriented calls
(stop, reset, version, et al).
BFILE is what you'll use for virtually all standard, fixed length Btrieve
----- files when not using extended calls; it will probably be your
workhorse.
BFIXED has structures to support ANY standard, fixed length Btrieve file.
------ BFixed is used in the CRUNCHx.PAS programs provided, which can 'clone
and squish' (remove dead space) from any standard, fixed length
Btrieve file. BFixed is useful for working on standard fixed length
files of an unknown nature when you need to perform *record* oriented
functions with no care about field definitions.
BFILEEXT is used for any Btrieve file for which you'll be using extended calls
-------- (note there is no specific support for the extended insert call, but
CRUNCH2.PAS and CRUNCH3.PAS contain examples of how to make such
calls).
Quite simply, your TYPE declarations define your Btrieve files as descendants
of one of the three file-oriented objects provided in BTP and described above.
Those declarations can include definitions of your file's field structures,
and provide the required data and key buffers. These programmer-defined
additions to the BTP objects are encapsulated in the descendant object, along
with the data fields inherited, which include a description of the file's
structure and the file's position block. Immediately upon instantiation of a
BTP descendant object, you have access to any file's structure and stats. You
also have an 'isolated' position block and the required buffers for Btrieve
calls.
In the case of a BFile descendant, you override (replace) a single object
method, and can make your Btrieve calls with just two parameters: the Btrieve
op code and key number. Use of the 4 get/step extended calls is only
marginally more difficult. In order to use extended calls, your object must
be a descendant of BFileExt, and must override two methods: one for standard
calls, and one for the extended calls. BFileExt includes pointers to two
collections, which are automatically initialized when the object's
constructor is called. The first collection is for the filter logic terms,
the second is for the field extractor specs. Your program simply inserts
items into these collections. The rest, including determining buffer lengths
and structuring the outgoing buffer, is handled internally by the object's
methods.
Note that this unit's initialization section does a HALT if the Btrieve
record manager isn't resident...you never have to code the test yourself.
Just use this unit!
USING THE BFILE OBJECT
----------------------
This unit gives you a technique, a shorthand way to get into Btrieve files
and manipulate them. For example, to open a file and encapsulate its stats
into your Btrieve file object, you need only do:
ObjectName^.Init(BDosFileName, Normal)
(BDosFileName is a valid Btrieve file name, and 'Normal' is the open
mode...see BTP.PAS for the various constants defined.)
Similary, to close a file, you simply do:
BStatus := ObJectName^.Close;
The BFile object defined herein is the heart of this unit. BFile is NOT an
abstract object!! It can be used as is, for example, if all you want to do
is to get stats (see STATS.PAS). However, if you wish to do any normal
record-oriented functions, you will want to extend the object by adding one
data element, a free-union variant record, to the BFile definition. This
record will contain your field definitions. Lastly, you are *required* to
override the BT function. Here's an example.
..............................................................................
TYPE
MyFields = record
case integer of
1: (Field1 : array[1..4] of char; (obviously use size and type)
Field2 : array[1..10] of char; (of your fields here! )
(rest of fields go here also)
KeyBuf : array[1..4] of char); (size to largest key length )
2: (DBuffer: array[1..14] of char);(size to record length )
3: (Position: array[1..2] of word);(high word returned first! )
end; (useful after a GET POSITION)
PMyObject = ^MyObject;
MyObject = object(BFile) (MyObject is now a descendant of BFile. )
Fields : MyFields;
function BT(OpCode, Key: integer); integer; virtual;
end;
VAR
MyFile : PMyObject;
(This is the required override of the BT function for all standard, fixed
length files. As the base object has no field structures, it cannot include
this function...you must override it by REPLACING it as shown here.)
function MyObject.BT(OpCode, Key:integer);integer;
begin (DBufferLen is reset here as it may need to)
DBufferLen := Specs.RecLen; (be changed on return from some ops. )
BT := Btrv(OpCode, PosBlk, Fields, DBufferLen, Fields.KeyBuf, Key);
end;
..............................................................................
The record variable itself should be used as the Btrieve data buffer
parameter. The data buffer length is reset to the file's record length before
making the call. The KeyBuf field of the MyFields record should be used in
all Btrieve calls as the Btrieve key buffer.
As you can see, your BFile descendant will incorporate the universal Btrieve
function call in a compact, elegant format. Thanks to OOP and encapsulation,
you are guaranteed that each descendant's own key and data buffers are used
since each object has its private copy of the .BT method.
As an aside, the .BT function cannot be fully incorporated into the base BFile
object since the base object has no data fields corresponding to your file's
fields, and thus cannot contain a properly sized key buffer or data buffer.
The override is necessary because neither the BFile object nor Btrieve itself
know anything about fields or the appropriate size for your buffers.
In fact, to assure you don't call the .BT function of BFile directly, BFile.BT
includes a call to TP6's Abstract method, which will crash your program with a
runtime error unless you replace it, presumably with the function as defined
and recommended here.
For your reference, the base BFile object is all of 975 bytes in size. The
BFile descendant used in EXAMPLE1.PAS, even with its additional methods and
data fields, is only 1025 bytes in size. In contrast, the BFixed object is
5320 bytes in size, due to its inclusion of maximum size buffers for a file
with records of up to the maximum size of 4090 and maximum key length of 255.
Assume a Btrieve file named 'DosFile'. To initialize the BFile descendant
defined above:
MyFile := new(PMyObject, Init('DosFile', Normal));
The BFile constructor/initialization method will do a Btrieve open operation,
using the open mode you provide as the second parameter to the Init call.
After the open operation, a stat operation is performed. The Btrieve
filespec and key specs, retrieved by the stat call, are incorporated into
BFile's data fields. Those stats can then be read out at will simply by
referencing the object's data fields in the following manner:
MyFile^.Specs.PageSize, or MyFile^.NumRecs, etc.
The stat fields defined in the object include the number of keys, the total
number of key segments, the page size, the number of records in the file
when the INIT was performed, the file flags...in short, the complete Btrieve
filespec. All other stats can be derived from the object's data fields if
needed. If you ever need to refresh the stat data to refresh the record
count, for example, you could simply:
BStatus := MyFile^.Close;
dispose(MyFile, Done);
MyFile := new(PMyObject, Init('DosFile', Normal));
HowMany := MyFile^.NumRecs;
Of course, you could always do a Stat call directly and deal with the
results, but the BTP way is so EASY!! MyFile^.NumRecs, a longint, is
available immediately after the Init call.
Btrieve operations with BTP are performed by calling the BT function with JUST
TWO PARAMETERS. Here's a simple step next call:
BStatus := MyFile^.BT(BStepNext, 2);
(BStatus is a public integer var from this unit. BStepNext is a public
constant. A number of public constants are defined in this unit that can
help make your code more readable.)
There is some nominal overhead in the BFile object. It allocates space for
the maximum of 24 keys/segments, and for an alternate collating sequence,
whether one is used in the file or not. This simply means a maximum of 633
bytes per open file could be wasted. This maximum would apply to a standard
Btrieve file that had just one unsegmented key. The figure of 368 is derived
as follows:
665 maximum bytes if the Btrieve max of 24 keys/
segments and an alternate collating sequence is used
- 16 bytes to hold the Btrieve file specs (stats)
- 16 bytes for the minimum required 1 Btrieve key spec
---
633 bytes maximum possible waste
In addition, my convention of supplying the BFile descendant with a built-in
key-buffer, arbitrarily sized to the length of the largest size key, could be
overhead. I don't mind that a bit, and I don't think you should. In my opinion,
the code space saved by using this unit's structures and techniques versus
the code resulted from using any other method is significant and more than
offsets some truly nominal memory costs.
The benefits of referencing any open Btrieve file and its components or stats
by name makes using the Btrieve record manager a snap. Add to your descendant
appropriate methods to get/set your fields and the key and data buffers and
you'll be much more productive.
Your app's code is responsible for stuffing key values into the key buffer,
getting or setting the data buffer, et al, as usual. However, you gain the
ease of accessing all Btrieve ops in a distinct shorthand, accessing those
buffers and fields by name, and can rest assured that all parameters are
passed and filled. See EXAMPLE1.PAS for a full working model of this
technique.
You will note that the examples sometimes break one of OOP's rules by
accessing data fields directly. In my own apps I will typically have at
least a couple of additional methods in my BFile descendants for getting and
setting the key buffers and data buffers...such as the following three:
(pass whatever string you want to stuff in the key buffer)
procedure MyObject.SetKeyBuf(KeyBufString:string);
var
Counter,
LengthKeyBufString : integer;
begin
LengthKeyBufString := length(KeyBufString);
if LengthKeyBufString > 12 then (replace '12' with length of your KeyBuf)
LengthKeyBufString := 12;
move(KeyBufString[1], Fields.KeyBuf, LengthKeyBufString);
if LengthKeyBufString < 12 then
for Counter := (LengthKeyBufString+1) to 12 do
Fields.KeyBuf[Counter] := ' '; (pad with trailing blanks)
end;
function MyObject.GetKeyBuf:string;
begin
GetKeyBuf := Fields.KeyBuf;
end;
function MyObject.GetDBuffer:string;
begin
GetDBuffer := Fields.DBuffer;
end;
USING THE BFILEEXT OBJECT
-------------------------
There were several assumptions coded into the BTP unit to make a working
structure for the get/step extended calls (see the declaration of data types
for BFileExt in the unit's source code):
1. That the required data buffer will never be more than 32767 bytes.
2. That values used for a filter's logic terms will never be longer
than 255 bytes.
Beyond these assumptions, which you can change if necessary by changing the
unit's source or by defining your own descendants, BTP enables you to make any
of the four get/step extended calls with ease. The best examples of how to do
so are in the example programs CRUNCH3.PAS and EXAMPLE2.PAS.
Admittedly, constructing outgoing buffers for Btrieve's extended calls can
be frustrating. Btrieve is hostile in this regard. BTP reduces this
drudgery to a couple of additional setup statements!
Recall that the outgoing buffer for these extended calls requires several
data structures occupying contiguous bytes in the buffer: a header, a
filter, optional filter logic terms, an extractor, and at least one field
extractor spec. After initializing the data buffer, you must calculate
the buffer length for the Btrieve call to be the larger of the outgoing or
incoming buffers! SHEESH! The BTP way of dealing with this is as follows:
1: HEADER is handled internally. Don't mess with it. Yes, even setting
the buffer length in the header is handled internally.
2: FILTER's fields MUST be assigned by your program.
3: FILTERSPEC is a TP collection that may or may not hold any objects,
depending on your program's needs.
4: EXTRACTOR's fields MUST be assigned by your program.
5: EXTRACTORSPEC is a TP collection that must hold at least one object.
6: EXTDBUFFER, the data buffer for these 4 extended calls, is handled
internally. Don't mess with it, except to use it as shown below and in
EXAMPLE2.PAS when you override BFileExt.BTExt.
7. Buffer structuring is totally transparent to your program. Simply
override the BFileExt.BTExt function as shown, and you're there!
Believe me, this took some work! The standard TP6 'ForEach' iterator
is used to take every item in the two collections and account for
them in structuring the outgoing data buffer.
The BFileExt.BTExt method must be overridden, with the override calling the
ancestor; i.e. your override must be of a standard form, and must *first* call
BFileExt.BTExt. This is because BFileExt.BTExt is what sets the buffer length
and constructs the buffer. This is in contrast to BFile.BT or BFileExt.BT,
which must be overridden by REPLACING them.
Since Btrieve's get/step extended calls permit use of filters based on field
values or on a user (program) supplied value, there are two constructors for
the logic term object: INITF (initialize to compare with field) and,
INITV (initialize to compare with value).
Refer to the BTP source code for a list of the parameters required by these
two constructors.
I strongly urge you to review EXAMPLE2.PAS for a full working model of using
extended calls with BTP. The following is a skeletal example.
.............................................................................
USES BTP;
TYPE
MyFields = record
case integer of
1: (First :array[1..10] of char;
Last :array[1..20] of char;
KeyBuf :array[1..20] of char); (sized to largest key)
2: (DBuffer :array[1..30] of char); (sized to rec length)
3: (Position:array[1..2] of word); (useful after a GET POS)
end;
PMyObject = ^MyObject;
MyObject = object(BFileExt)
Fields: MyFields;
function BT(OpCode, Key: integer): integer; virtual;
function BTExt(OpCode, Key: integer): integer; virtual;
end;
VAR
MyFile = PMyObject;
Counter = integer;
X = string;
Value = TCharArray; {TCharArray is a data type in BTP...an array of
255 chars.}
{NOTE that you can use these overrides verbatim for each and every descendant
if you follow the naming conventions outlined in this documentation and in
the example programs.}
function MyObject.BT(OpCode, Key: integer): integer;
begin
DBufferLen := Specs.RecLen;
BT := Btrv(OpCode, PosBlk, Fields, DBufferLen, Fields.KeyBuf, Key);
end;
function MyObject.BTExt(OpCode, Key: integer): integer;
begin
BStatus := BFileExt.BTExt(OpCode, Key); (MUST call ancestor method!!!)
BTExt := Btrv(OpCode, PosBlk, ExtDBuffer^.Entire, DBufferLen,
Fields.KeyBuf, Key);
end;
BEGIN
MyFile := new(PMyObject, Init('Example', ReadOnly));
{WHAM! File 'Example' is now open in read only mode, with it's structure and
stats encapsulated in fields of the object.}
with MyFile^ do {Perform required data initializations.}
begin {This one code section takes care of }
Filter.MaxSkip := 50; {initializing the filter and extractor.}
Filter.NumLogicTerms := 2;
Extractor.NumRecords := 5;
Extractor.NumFields := 1;
end;
{Now specify filter logic terms. We'll setup filter to use 'Leon' (a Last
Name) as a value for filtering.}
X := 'Leon';
for Counter := 1 to length(X) do
Value[Counter] := X[Counter];
for Counter := (length(X) + 1) to 255 do
Value[Counter] := ' '; {Pad the array w/trailing blanks.}
{Now use one statement to initialize the filter logic term object and insert
it into the FilterSpec collection. Note use of the INITV constructor vs
the INITF constructor.}
with MyFile^.FilterSpec^ do
insert(new(PFilterSpec, InitV(BString, 20, 10, Equal, NextTermAnd, Value)));
{Now setup another logic term to specify a first name search condition!}
X := 'John';
for Counter := 1 to length(X) do
Value[Counter] := X[Counter];
for Counter := (length(X) + 1) to 255 do
Value[Counter] := ' ';
{Here's the statement to initialize the second filter logic term object and
insert it into the FilterSpec collection provided and initialized for you.}
with MyFile^.FilterSpec^ do
insert(new(PFilterSpec, InitV(BString, 10, 0, Equal, LastTerm, Value)));
{Let's set up to extract an entire record, not just particular fields.}
{1st parameter to the Init is the length of the item to extract (here the
record length). 2nd parameter is offset from which to begin extraction}
with MyFile^.ExtractorSpec^ do
insert(new(PExtSpec, Init(MyFile^.Specs.RecLen, 0)));
{Oh, yea...need to establish a position before using these calls, so let's do
a Get First before the extended call!}
BStatus := MyFile^.BT(BGetFirst, Zero);
{Now let's get on with it and make the extended call...}
BStatus := MyFile^.BTExt(BGetNextExt, Zero);
{Take advantage of the data types and Pascal record structures provided to
see how many records were returned in this call.}
writeln('Number of records returned with name ''John Leon'' is ',
MyFile^.ExtDBuffer^.NumRecs);
{Don't forget to close the file and dispose of the dynamic object. The
object's Done destructor will also dispose of the collections for you.}
BStatus := MyFile^.Close;
dispose(MyFile, Done);
END.
.............................................................................
This should be enough to get you started using BTP with both non-extended
and the get/step extended calls. Again, refer to the unit's source code and
to the example programs for a fuller understanding of the BTP structures and
technique.
ABOUT THE EXAMPLE PROGRAMS
--------------------------
The README.1ST file for a list and description of the example programs. They
should be extremely helpful in getting you up to speed with the BTP product.
Several of them are utility programs you can use "out of the box"!
PARTING SHOTS
-------------
ENJOY BTP PROGRAMMING! BTP is not free...it is shareware. Yours with no
obligation for a 30 day trial period, you are expected to remit the $25
registration fee if you use BTP. Please refer to the README.1ST file for
license terms and remittance instructions.
The most current version of BTP can usually be found in 3 places on Compu-
serve if not available to you through your favorite BBS or shareware catalog:
Borland's Programmer's Forum (go BPROGA), in download library 1
IBM Programmer's Forum (go IBMPRO), in download library 0 (new uploads) or
download library 5 (other languages)
Novell Netwire (go NOVA), download library 12 (independent developers),
download library 14 (public domain),
download library 17 (other new uploads)
Even if you choose not to register your copy of BTP, I would like to hear
from you about your experiences with and reaction to using this unit. In
addition, of course, constructive criticism and suggestions are always
welcome.
John C. Leon
3807 Wood Gardens Court
Kingwood, TX 77339
713-359-3641 (residence)
CIS #72426,2077